type IdType = string | number;

interface Data {
    id: IdType;
}

class ArraySet<T extends Data> {
    private _idMap: Map<IdType, number> = new Map();
    private _list: T[] = [];

    constructor(values?: T[]) {
        if (values) {
            this._list = values;
            for (let i = 0; i < values.length; i += 1) {
                this._idMap.set(values[i].id, i);
            }
        }
    }

    set(item: T) {
        const index = this._idMap.get(item.id);
        if (typeof index === 'number') {
            this._list[index] = item;
        } else {
            this._list.push(item);
            this._idMap.set(item.id, this._list.length - 1);
        }
    }

    delete(item: T) {
        const id = item.id;
        const index = this._idMap.get(id);
        if (typeof index === 'number') {
            this._list.splice(index, 1);
            this._idMap.delete(id);
            this._updateIndex(index);
        }
    }

    private _updateIndex(startIndex: number) {
        for (let i = startIndex; i < this._list.length; i += 1) {
            this._idMap.set(this._list[i].id, i);
        }
    }

    size() {
        return this._list.length;
    }

    has(item: T) {
        return this._idMap.has(item.id);
    }

    getValues() {
        return this._list;
    }

    getById(id: string) {
        const index = this._idMap.get(id);
        if (typeof index === 'number') {
            return this._list[index];
        }
    }

    clear() {
        this._idMap.clear();
        this._list.length = 0;
    }

    shift() {
        const item = this._list.shift();
        if (item) {
            this._idMap.delete(item.id);
            this._updateIndex(0);
        }
        return item;
    }

    pop() {
        const item = this._list.pop();
        if (item) {
            this._idMap.delete(item.id);
        }
        return item;
    }

    valuesConcat(set: ArraySet<T>) {
        return this._list.concat(set._list);
    }

    get(index: number) {
        return this._list[index];
    }
}

export default ArraySet;
